package com.echovin.esb.gateway.exception;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.echovin.esb.gateway.locator.GatewayErrorResponseLocator;
import com.echovin.esb.gateway.model.GatewayAccessLog;
import com.echovin.esb.gateway.model.ResultBody;
import com.echovin.esb.gateway.service.GatewayAccessLogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * 自定义异常处理
 *
 * <p>异常时用JSON/XML代替HTML异常信息<p>
 *
 * @author dym
 */
@Slf4j
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
    @Autowired
    GatewayErrorResponseLocator gatewayErrorResponseLocator;
    @Autowired
    GatewayAccessLogService accessLogService;
    public static final String GATEWAY_ACCESS_LOG_KEY = "GatewayAccessLog";

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable e) {
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        MediaType contentType = headers.getContentType();
        // 异常封装为model，处理code、msg、HttpStatus等信息
        ResultBody resultBody = GlobalExceptionResolve.resolveException(e, exchange.getRequest().getURI().getPath());
        final String body;
        // 配置返回格式
        // 1，取服务提供方配置的根据状态码单项配置
        // 2，取服务提供方配置的默认全局配置
        // 3，Gateway默认全局配置
        String content = gatewayErrorResponseLocator.getErrorResponse(resultBody.getPath(), String.valueOf(resultBody.getCode()));
        if (!StringUtils.isEmpty(content)) {
            StringBuilder builder = new StringBuilder(content);
            replaceAll(builder, "$code$", String.valueOf(resultBody.getCode()));
            replaceAll(builder, "$msg$", resultBody.getMessage());
            body = builder.toString();
        } else {
            // 为空走默认
            if (MediaType.APPLICATION_XML.equals(contentType)) {
                body = toXMLString(resultBody);
            } else {
                body = toJSONString(resultBody);
            }
        }

        // 记录日志
        log.error("gateway捕获异常:{}", resultBody.toString());
        GatewayAccessLog accessLog = exchange.getAttribute(GATEWAY_ACCESS_LOG_KEY);
        accessLogService.insert(accessLog);

        // 返回response
        return Mono.defer(() -> {
            return Mono.just(exchange.getResponse());
        }).flatMap((response) -> {
            response.setStatusCode(HttpStatus.valueOf(resultBody.getHttpStatus()));
            response.getHeaders().setContentType(contentType);
            DataBufferFactory dataBufferFactory = response.bufferFactory();
            DataBuffer buffer = dataBufferFactory.wrap(body.getBytes(Charset.defaultCharset()));
            return response.writeWith(Mono.just(buffer)).doOnError((error) -> {
                DataBufferUtils.release(buffer);
            });
        });
    }

    /**
     * obj -> json str
     *
     * @param obj
     * @return
     */
    public static String toJSONString(Object obj) {
        try {
            StringBuffer buffer = new StringBuffer();
            buffer.append(JSONObject.toJSONString(obj));
            return buffer.toString();
        } catch (Exception e) {
            log.error("gateway全局异常处理obj转json失败:{}", e.getMessage());
            return "";
        }
    }

    /**
     * obj -> xml str
     *
     * @param obj
     * @return
     */
    public static String toXMLString(Object obj) {
        try {
            StringBuffer buffer = new StringBuffer();
            buffer.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
            buffer.append("<" + "responseinfo" + ">");
            JSONObject jObj = (JSONObject) JSON.toJSON(obj);
            jsonToXmlstr(jObj, buffer);
            buffer.append("</" + "responseinfo" + ">");
            return buffer.toString();
        } catch (Exception e) {
            log.error("gateway全局异常处理obj转xml失败:{}", e.getMessage());
            return "";
        }
    }

    /**
     * json obj -> xml str
     *
     * @param jObj   the j obj
     * @param buffer the buffer
     * @return the string
     */
    public static String jsonToXmlstr(JSONObject jObj, StringBuffer buffer) {
        Set<Map.Entry<String, Object>> se = jObj.entrySet();
        for (Iterator<Map.Entry<String, Object>> it = se.iterator(); it.hasNext(); ) {
            Map.Entry<String, Object> en = it.next();
            if (en == null || en.getValue() == null) {
                continue;
            }
            if (en.getValue().getClass().getName().equals("com.alibaba.fastjson.JSONObject")) {
                buffer.append("<" + en.getKey() + ">");
                JSONObject jo = jObj.getJSONObject(en.getKey());
                jsonToXmlstr(jo, buffer);
                buffer.append("</" + en.getKey() + ">");
            } else if (en.getValue().getClass().getName().equals("com.alibaba.fastjson.JSONArray")) {
                JSONArray jarray = jObj.getJSONArray(en.getKey());
                for (int i = 0; i < jarray.size(); i++) {
                    buffer.append("<" + en.getKey() + ">");
                    JSONObject jsonobject = jarray.getJSONObject(i);
                    jsonToXmlstr(jsonobject, buffer);
                    buffer.append("</" + en.getKey() + ">");
                }
            } else {
                buffer.append("<" + en.getKey() + ">" + en.getValue());
                buffer.append("</" + en.getKey() + ">");
            }
        }
        return buffer.toString();
    }

    /**
     * 实现StringBuilder的replaceAll
     *
     * @param stb
     * @param oldStr 被替换的字符串
     * @param newStr 替换oldStr
     * @return
     */
    public static StringBuilder replaceAll(StringBuilder stb, String oldStr, String newStr) {
        if (stb == null || oldStr == null || newStr == null || stb.length() == 0 || oldStr.length() == 0) {
            return stb;
        }
        int index = stb.indexOf(oldStr);
        if (index > -1 && !oldStr.equals(newStr)) {
            int lastIndex = 0;
            while (index > -1) {
                stb.replace(index, index + oldStr.length(), newStr);
                lastIndex = index + newStr.length();
                index = stb.indexOf(oldStr, lastIndex);
            }
        }
        return stb;
    }

}